/*
 * $Id: runner.cpp 22 2010-12-30 20:19:21Z olexiy.palinkash@gmail.com $
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <time.h>
#include <getopt.h>
#include <errno.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <fcntl.h>
#include <signal.h>
#include <grp.h>
#include <pwd.h>
#include <pthread.h>
#include <string>
#include <vector>
#include <string>

using namespace std;

#define BUF_SIZE (64 * 1024)

/* Exit codes */
#define EXIT_OK			50
#define EXIT_TLE		51
#define EXIT_MLE		52
#define EXIT_OLE		53
#define EXIT_RE			54
#define EXIT_IE			55

#define EXIT_UNKNOWN	60

// application (executable) name
char *app_name = NULL;

// program to execute
char *program_name = NULL;

// root directory
char *root_directory = NULL;

// pid of child process
pid_t child_pid = 0;

// `verbose` flag
bool verbose = false;

// cpu time limit (in milliseconds)
int cpu_limit = -1;

// real time limit (in milliseconds)
int real_limit = -1;

// memory limit (in bytes)
int memory_limit = -1;

// output limit (in bytes)
int output_limit = -1;

// actual time, memory and output usage
int cpu_used = -1;
int real_used = -1;
int memory_used = -1;
static volatile int output_used = -1;

struct timespec real_time_start, real_time_finish;

//
int exit_code = 0;

//
int signal_number = -1;

//
char *signal_name = NULL;

//
bool check_memory_postfactum = false;

// files to redirect standard I/O stream to
char *input_file = NULL;//"input.txt";
char *output_file = NULL;//"/dev/null";
char *error_file = NULL;//"/dev/null";

char *log_filename = (char *)"runner.log";//"/home/alt/runner-log.txt";
FILE *log_file = NULL;

void log_init()
{

}

void log_finalize()
{
	if (NULL != log_file)
	{
		fclose(log_file);
	}
}

int log(const char *fmt, ...)
{
	va_list ap; /* special type for variable    */
	char format[100000]; /* argument lists               */
	int count = 0;
	int i, j; /* Need all these to store      */
	char c; /* values below in switch       */
	double d;
	unsigned u;
	char *s;
	void *v;

	va_start(ap, fmt); /* must be called before work   */
	while (*fmt)
	{
		for (j = 0; fmt[j] && fmt[j] != '%'; j++)
			format[j] = fmt[j]; /* not a format string          */
		if (j)
		{
			format[j] = '\0';
			count += fprintf(log_file, format);
			fmt += j;
		}
		else
		{
			for (j = 0; !isalpha(fmt[j]); j++)
			{ /* find end of format specifier */
				format[j] = fmt[j];
				if (j && fmt[j] == '%') /* special case printing '%'    */
					break;
			}
			format[j] = fmt[j]; /* finish writing specifier     */
			format[j + 1] = '\0'; /* don't forget NULL terminator */
			fmt += j + 1;

			switch (format[j])
			{ /* cases for all specifiers     */
			case 'd':
			case 'i': /* many use identical actions   */
				i = va_arg(ap, int); /* process the argument         */
				count += fprintf(log_file, format, i); /* and log it                 */
				break;
			case 'o':
			case 'x':
			case 'X':
			case 'u':
				u = va_arg(ap, unsigned);
				count += fprintf(log_file, format, u);
				break;
			case 'c':
				c = (char) va_arg(ap, int); /* must cast!                   */
				count += fprintf(log_file, format, c);
				break;
			case 's':
				s = va_arg(ap, char *);
				count += fprintf(log_file, format, s);
				break;
			case 'f':
			case 'e':
			case 'E':
			case 'g':
			case 'G':
				d = va_arg(ap, double);
				count += fprintf(log_file, format, d);
				break;
			case 'p':
				v = va_arg(ap, void *);
				count += fprintf(log_file, format, v);
				break;
			case 'n':
				count += fprintf(log_file, "%d", count);
				break;
			case '%':
				count += fprintf(log_file, "%%");
				break;
			default:
				fprintf(stderr, "Invalid format specifier in log().\n");
			}
		}
	}
	va_end(ap); /* clean up                     */
	fprintf(log_file, "\n");
	fflush(log_file);
	return count;
}

// pipes for I/O redirection
int pipein[2], pipeout[2], pipeerr[2];

// possible options
const struct option long_options[] =
// TODO: add following params
		// home directory
		// fork limit
		// chroot
		// log file
		// xml log
		{
		{ "help", 0, NULL, 'h' },
		{ "verbose", 0, NULL, 'v' },

		{ "memory-limit", 1, NULL, 'm' },
		{ "check-memory-limit-postfactum", 0, NULL, 'p' },

		{ "output-limit", 1, NULL, 'o' },
		{ "cpu-time-limit", 1, NULL, 'c' },
		{ "real-time-limit", 1, NULL, 'r' },

		{ "chroot", 1, NULL, 'd' },
		{ "input-file", 1, NULL, 'I' },
		{ "output-file", 1, NULL, 'O' },
		{ "error-file", 1, NULL, 'E' },
		{ NULL, 0, NULL, 0 }, };

// short option string
const char *short_options = "hpvm:c:r:o:I:O:E:d:";

void usage()
{
	printf("USAGE: %s [options] <executable>\n", app_name);
}

void help()
{
	usage();
	printf(
			"Options:\n"
				"\t-h, --help		Help\n"
				"\t-v, --verbose		Verbose output\n"
				"\t-m, --memory-limit:	Limit memory usage in bytes\n"
				"\t-p, --check-memory-limit-after:	Not to limit memory usage, but check is afterwards\n"
				"\t-o, --output-limit:	Limit size of output files in bytes\n"
				"\t-c, --cpu-time-limit:	Limit CPU time in milliseconds\n"
				"\t-r, --real-time-limit:	Limit real time in milliseconds\n"
				"\t-d, --chroot:	Set home directory\n"
				"\t-I, --input-file:	Set program's input file\n"
				"\t-O, --output-file:	Set program's output file\n"
				"\t-E, --error-file:	Set program's error file\n");
}

void fatal_exit(const char *cause, const char *message, int errcode)
{
	fprintf(stderr, "FATAL INTERNAL ERROR\n");
	fprintf(stderr, "%s: %s\n", cause, message);
	log("FATAL INTERNAL ERROR");
	log("%s: %s", cause, message);
	log_finalize();
	exit(errcode);
}

void set_limit(int limit_name, int limit_value)
{
	if (limit_value > 0)
	{
		static struct rlimit limit;
		limit.rlim_cur = limit_value;
		limit.rlim_max = limit_value;

		if (setrlimit(limit_name, &limit) < 0)
		{
			fatal_exit("setrlimit()", strerror(errno), -1);
		}
	}
}

// FIXME: params shouldn't contain spaces
char** prepare_params(char **argv)
{
	char **pstr = argv;
	vector<string> res;
	while (*pstr)
	{
		char *s = *pstr;
		char *p = s;
		while (*p)
		{
			while (*p == ' ')
				p++;
			if (*p == 0)
				break;
			string t;
			while (*p && *p != ' ')
				t.push_back(*p++);
			res.push_back(t);
		}
		pstr++;
	}

	char **res2 = (char**) malloc(sizeof(char*) * (res.size() + 1));
	for (unsigned i = 0; i < res.size(); i++)
	{
		res2[i] = strdup(res[i].c_str());
	}
	res2[res.size()] = NULL;
	return res2;
}

void do_child(char **argv)
{
	/* Setting limits (if any) */

	// CPU limit
	if (cpu_limit > 0)
	{
		// CPU time limit is given in milliseconds, while kernel limit must be given in seconds,
		// so rounding it up to the nearest second
		set_limit(RLIMIT_CPU, (cpu_limit + 1050) / 1000);
	}
	log("child::CPU time limit is set to %d second(s)", (cpu_limit + 1050) / 1000);

	// memory limit
	if (!check_memory_postfactum)
	{
		set_limit(RLIMIT_AS, memory_limit);
		set_limit(RLIMIT_DATA, memory_limit);
		set_limit(RLIMIT_STACK, memory_limit);
	}
	log("child::Memory limit is set to %d bytes", memory_limit);

	// limit size of files
	set_limit(RLIMIT_FSIZE, output_limit);

	// TODO: review
	//if (access(argv[0], X_OK) < 0)
	//{
	//	//fatal_exit("access", strerror(errno), -1);
	//}

	setsid();

	/* Standard input redirection */
	if (NULL != input_file)
	{
		log("child::Managing input_file pipes");
		if (close(pipein[1]) != 0)
			fatal_exit("close(pipein[1])", strerror(errno), -1);
		if (dup2(pipein[0], STDIN_FILENO) < 0)
			fatal_exit("dup2(pipein[0])", strerror(errno), -1);
	}

	/* Standard output redirection */
	if (NULL != output_file)
	{
		log("child::Managing output_file pipes");
		if (close(pipeout[0]) != 0)
			fatal_exit("close(pipeout[0])", strerror(errno), -1);
		if (dup2(pipeout[1], STDOUT_FILENO) < 0)
			fatal_exit("dup2(pipeout[1])", strerror(errno), -1);
	}

	/* Standard error redirection */
	if (NULL != error_file)
	{
		log("child::Managing error_file pipes");
		if (close(pipeerr[0]) != 0)
			fatal_exit("close(pipeerr[0])", strerror(errno), -1);
		if (dup2(pipeerr[1], STDERR_FILENO) < 0)
			fatal_exit("dup2(pipeerr[1])", strerror(errno), -1);
	}

	//clock_gettime(CLOCK_MONOTONIC, &real_time_start);

	char **params = prepare_params(argv);

	{
		log("child::Executing \"%s\"", *argv);
		char **params = argv;
		params++;
		if (*params)
		{
			log("with parameters");
			while (*params)
			{
				log("   %s", *params++);
			}
		}
	}

	// executing process
	execvp(params[0], params);

	// inaccessible code
	fatal_exit("execvp", strerror(errno), -1);
}

static volatile int realtime_fired = 0;

static void sig_alarm(int i __attribute__((unused)))
{
	realtime_fired = 1;
}

/*
 * Converts lower-case ASCII characters in string to upper-case
 */
void strtouppercase(char *s)
{
	char *p = s;
	while (*p)
	{
		if (*p >= 'a' && *p <= 'z')
			*p = *p - 'a' + 'A';
		p++;
	}
}

/*
 * Parses string which defines memory limit
 * in format <NonnegativeNumber>[<Prefix>]
 * where <Prefix> is of
 *   K, KB - kilobytes
 *   M, MB - megabytes
 *   G, GB - gigabytes
 * prefixes are case-insensitive
 *
 * if succeed res contains numbed of bytes
 *
 * return boolean indicating successful parsing
 */
bool parse_memory_limit(const char *str, int &res)
{
	int val = 0;
	if (sscanf(str, "%d", &val) != 1)
		return 0;
	char *s = strdup(str);
	strtouppercase(s);
	int len = strlen(s);

	if (s[len - 1] == 'B')
		len--;
	if (s[len - 1] == 'K')
		val *= 1024;
	if (s[len - 1] == 'M')
		val *= 1024 * 1024;
	if (s[len - 1] == 'G')
		val *= 1024 * 1024 * 1024;
	if (val > 0)
		res = val;
	free(s);
	return val > 0;
}

/*
 * Parses string which defines memory limit
 * in format <NonnegativeNumber>[<Prefix>]
 * where <Prefix> is of
 *   ms - milliseconds
 *   m - minutes
 *   s - seconds
 *   h - hours
 * prefixes are case-insensitive
 *
 * if succeed res contains number of millisecond
 *
 * return boolean indicating successful parsing
 */
bool parse_time_limit(const char *str, int &res)
{
	int val = 0;
	if (sscanf(str, "%d", &val) != 1)
		return 0;

	char *s = strdup(str);
	strtouppercase(s);
	int len = strlen(s);

	if (s[len - 1] == 'S' && s[len - 2] == 'M')
		val *= 1;
	else if (s[len - 1] == 'S')

		val *= 1000;
	else if (s[len - 1] == 'M')
		val *= 60 * 1000;
	else if (s[len - 1] == 'H')
		val *= 60 * 60 * 1000;
	if (val > 0)
		res = val;
	free(s);
	return val > 0;
}

const char* itoa(int val)
{
	char *str;
	asprintf(&str, "%d", val);
	return str;
}

void read_from_pipe_to_file(int *pipe, const char *filename)
{
	/* Close our copy of the write end of the file descriptor. */
	if (close(pipe[1]) != 0)
		fatal_exit("close(pipe[1])", strerror(errno), -1);

	FILE* from_pipe, *to_file;
	/* Convert the read file descriptor to a FILE object, and read from it. */

	from_pipe = fdopen(pipe[0], "r");
	if (NULL == from_pipe)
		fatal_exit("fdopen(pipe[0], 'r');", strerror(errno), -1);

	void *buffer = malloc(BUF_SIZE);

	log("read_from_pipe_to_file::opening output file for writing: %s", filename);
	int fd = open(filename, O_WRONLY | O_CREAT | O_SYNC);
	//to_file = //fopen(filename, "w");
	//if (NULL == to_file)
		//fatal_exit("fopen(filename, 'w');", strerror(errno), -1);

	while (!feof(from_pipe))
	{
		int count = fread(buffer, 1, BUF_SIZE, from_pipe);
		if (count > 0)
			output_used += count;
		// TODO: check
		if (count < 0)
		{
			fatal_exit("fread(buffer, 1, BUF_SIZE, from_pipe)", strerror(errno), -1);
		}
		else if (count > 0)
		{

			if (write(fd, buffer, count) != count)
				log("!!!!!!!!!!! Error: from pipe - %d, to file - %d", count, to_file);
			if (fsync(fd) != 0)
				log("!!!!!!!!!!! Fflush = %s", strerror(errno));
			//log("%s", (char*)buffer);
			//if (fwrite(buffer, 1, count, to_file) != count)
			//log("!!!!!!!!!!! Error: from pipe - %d, to file - %d", count, to_file);
			//if (fflush(to_file) != 0)
				//log("!!!!!!!!!!! Fflush = %s", strerror(errno));
		}
	}
	/* Close file descriptor of pipe's read nd */
	fclose(from_pipe);
	/* Close our copy of the read end of the file descriptor. */
	close(pipe[0]);

	/* Close output file */
	if (fsync(fd) != 0)
		log("!!!!!!!!!!! Fflush = %s", strerror(errno));
	if (close(fd) != 0)
		log("!!!!!!!!!!! Fclose = %s", strerror(errno));

	chmod(filename, 0644);
	log("Freeing buffer %s", filename);
	free(buffer);
}

void write_to_pipe_from_file(int *pipe, const char *filename)
{
	/* Close our copy of the read end of the file descriptor. */
	if (close(pipe[0]) != 0)
		fatal_exit("close(pipe[0])", strerror(errno), -1);

	/* File descriptors */
	FILE* to_pipe, *from_file;

	/* Convert the write file descriptor to a FILE object, and write to it. */
	to_pipe = fdopen(pipe[1], "w");

	//void *buffer = malloc(BUF_SIZE);
	char buffer[BUF_SIZE];

	log("write_to_pipe_from_file::opening input file for reading: %s", filename);
	from_file = fopen(filename, "r");
	if (NULL == from_file)
		fatal_exit("fopen", strerror(errno), -1);

	while (!feof(from_file))
	{
		int count = fread(buffer, 1, BUF_SIZE, from_file);

		//log("write_to_pipe_from_file::fread - %d bytes", filename, count);
		// TODO: check
		if (count < 0)
		{
			fatal_exit("fread(buffer, 1, BUF_SIZE, from_file)",
					strerror(errno), -1);
		}
		fwrite(buffer, 1, count, to_pipe);
		fflush(to_pipe);
	}
	fclose(to_pipe);
	fclose(from_file);

	/* Close our copy of the write end of the file descriptor. */
	close(pipe[1]);
}

pthread_t thread_input_writer = 0, thread_output_reader = 0,
		thread_error_reader = 0;

void* thread_input_writer_func(void *unused)
{
	/* Sending data to child's standard input */
	if (NULL != input_file)
	{
		write_to_pipe_from_file(pipein, input_file);
	}
	return NULL;
}

void* thread_output_reader_func(void *unused)
{
	/* Reading data from child's standard output */
	if (NULL != output_file)
	{
		read_from_pipe_to_file(pipeout, output_file);
	}
	return NULL;
}

void* thread_error_reader_func(void *unused)
{
	/* Reading data from child's standard error */
	if (NULL != error_file)
	{
		read_from_pipe_to_file(pipeerr, error_file);
	}
	return NULL;
}

void parse_command_line(int argc, char *argv[])
{
	/* Parse command line parameters */
	int next_option;
	do
	{
		next_option
				= getopt_long(argc, argv, short_options, long_options, NULL);
		switch (next_option)
		{
		/* Print help message */
		case 'h':
			help();
			exit(0);
			break;

			/* Set verbose flag */
		case 'v':
			verbose = true;
			break;

			/* Set memory limit */
		case 'm':
			if (!parse_memory_limit(optarg, memory_limit))
			{
				usage();
				exit(1);
			}
			break;

		case 'p':
			check_memory_postfactum = true;
			break;

			/* Set output limit */
		case 'o':
			if (!parse_memory_limit(optarg, output_limit))
			{
				usage();
				exit(1);
			}
			break;

			/* Set CPU time limit */
		case 'c':
			if (!parse_time_limit(optarg, cpu_limit))
			{
				usage();
				exit(1);
			}
			break;

			/* Set real time limit */
		case 'r':
			if (!parse_time_limit(optarg, real_limit))
			{
				usage();
				exit(1);
			}
			break;

			/* Set root directory */
		case 'd':
			root_directory = optarg;
			break;

			/* Set input file */
		case 'I':
			input_file = optarg;
			break;

			/* Set output file */
		case 'O':
			output_file = optarg;
			break;

			/* Set error file */
		case 'E':
			error_file = optarg;
			break;

			/* No more options */
		case -1:
			break;

			/* Unknown option or error */
		case '?':
			printf("?\n");
			usage();
			exit(1);
			break;

		default:
			break;
		}
	} while (next_option != -1);
}

int do_parent()
{
	/* Starting threads to send/receive data from pipes */
	if (NULL != input_file)
	{
		int errNum = pthread_create(&thread_input_writer, NULL, &thread_input_writer_func, NULL);
		log("parent::Creating thread for input_writer: %s", errNum ? "ERROR" : "OK");
		if (0 != errNum)
			fatal_exit("pthread_create(thread_input_writer)", strerror(errno), -1);
	}

	if (NULL != output_file)
	{
		int errNum = pthread_create(&thread_output_reader, NULL, &thread_output_reader_func, NULL);
		log("parent::Creating thread for output_reader: %s", errNum ? "ERROR" : "OK");
		if (0 != errNum)
			fatal_exit("pthread_create(thread_output_reader)", strerror(errno), -1);
	}

	if (NULL != error_file)
	{
		int errNum = pthread_create(&thread_error_reader, NULL, &thread_error_reader_func, NULL);
		log("parent::Creating thread for error_reader: %s", errNum ? "ERROR" : "OK");
		if (0 != errNum)
			fatal_exit("pthread_create(thread_error_reader)", strerror(errno), -1);
	}

	pid_t res;
	int status;
	char* term_reason = NULL;
	struct rusage res_usage;

	if (real_limit > 0)
	{
		struct sigaction action;
		memset(&action, 0, sizeof(action));

		action.sa_handler = sig_alarm;
		int errNum = sigaction(SIGALRM, &action, NULL);
		log("parent::Creating sigaction for real_limit (%d sec): %s\n", (real_limit + 1050) / 1000, errNum ? "ERROR" : "OK");
		if (0 != errNum)
			fatal_exit("sigaction", strerror(errno), -1);

		alarm((real_limit + 1050) / 1000);
	}

	log("before waitpid");
	while ((res = waitpid(child_pid, &status, 0)) < 0)
	{
		log("inside waitpid");
		if (errno == EINTR && realtime_fired)
		{
			log("parent::Killing child processes because of realtime fired\n");
			if (kill(-child_pid, SIGKILL) < 0)
				fatal_exit("kill(-child_pid, SIGKILL)", strerror(errno), -1);

			if (kill(-child_pid, SIGCONT) < 0) /* in case of STOPed child. */
				fatal_exit("kill(-child_pid, SIGCONT)", strerror(errno), -1);
		}
		else
		{
			fatal_exit("waitpid", strerror(errno), -1);
		}
	}
	log("after waitpid");

	if (real_limit > 0)
	{
		log("parent::alarm(0)");
		alarm(0);
	}

	log("parent::Killing spawned processes");
	/* Nuke any other processes that may have been spawned. */
	if (kill(-child_pid, SIGKILL) < 0 && errno != ESRCH)
		fatal_exit("kill(SIGKILL)", strerror(errno), -1);
	if (kill(-child_pid, SIGCONT) < 0 && errno != ESRCH)
		fatal_exit("kill(SIGCONT)", strerror(errno), -1);

	/* determine _what_ caused the process to die. */
	if (WIFEXITED(status))
	{
		log("parent::Child process exited with exitcode: %d", WEXITSTATUS(status));
		signal_number = 0;
		asprintf(&signal_name, "none");
		exit_code = WEXITSTATUS(status);
		asprintf(&term_reason, "normal exicode=%d", WEXITSTATUS(status));
	}
	else if (WIFSIGNALED(status))
	{
		signal_number = WTERMSIG(status);
		signal_name = strsignal(signal_number);
		log("parent::Child process exited by signal %d = %s", signal_number, signal_name);
		if (!signal_name)
		{
			asprintf(&signal_name, "unknown");
		}
		asprintf(&term_reason, "signal signum=%d signame=%s", signal_number,
				signal_name);
	}
	else
	{
		log("parent::!!! UNKNOWN EXIT REASON !!!");
		signal_number = -1;
		asprintf(&signal_name, "unknown");
		asprintf(&term_reason, "uknnown");
	}

	if (getrusage(RUSAGE_CHILDREN, &res_usage) == 0)
	{
		unsigned totaltime = 0;
		totaltime += res_usage.ru_utime.tv_sec * 1000000;
		totaltime += res_usage.ru_stime.tv_sec * 1000000;
		totaltime += res_usage.ru_utime.tv_usec;
		totaltime += res_usage.ru_stime.tv_usec;
		totaltime = (totaltime + 500) / 1000; /* round to closest millisec. */

		memory_used = res_usage.ru_maxrss * 1024;
		cpu_used = totaltime;
	}
	else
	{
		// FIXME: error occurred
		memory_used = -1;
		cpu_used = -1;
	}

	/* Calculating real time */
	//gettimeofday(&real_time_finish, NULL);
	//long long usec  = real_time_finish.tv_sec * 1000000LL  + real_time_finish.tv_usec
	//- 1000000LL * real_time_start.tv_sec - real_time_start.tv_usec;

	// real_used = (usec / 1000.0) + 0.5;
	// clock_gettime();
	clock_gettime(CLOCK_MONOTONIC, &real_time_finish);

	long long usec = (real_time_finish.tv_sec - real_time_start.tv_sec)
			* 1000000000LL + real_time_finish.tv_nsec - real_time_start.tv_nsec;

	real_used = (usec + 500000) / 1000000;
	real_used = 0;

	char *result;

	int own_exit_code = EXIT_UNKNOWN;

	if (cpu_limit > 0 && cpu_limit < cpu_used)
	{
		asprintf(&result, "TLE - Time Limit Exceeded (CPU time limit exceeded)");
		own_exit_code = EXIT_TLE;
	}
	// TODO: fix real time estimation
	else if (/*realtime_fired && */real_limit > 0 && real_limit < real_used)
	{
		asprintf(&result,
				"TLE - Time Limit Exceeded (Real time limit exceeded)");
		own_exit_code = EXIT_TLE;
	}
	else if (memory_limit > 0 && memory_limit < memory_used)
	{
		asprintf(&result, "MLE - Memory Limit Exceeded");
		own_exit_code = EXIT_MLE;
	}
	else if (0 != signal_number)
	{
		switch (signal_number)
		{
		case -1:
			asprintf(&result, "IE - Internal Error");
			own_exit_code = EXIT_IE;
			break;

		case 11:
			asprintf(&result, "RE - Runtime Error (Segmentation fault)");
			own_exit_code = EXIT_RE;
			break;

		case 8:
			asprintf(&result, "RE - Runtime Error (Floating point exception)");
			own_exit_code = EXIT_RE;
			break;

		case 25:
			asprintf(&result, "OLE - Output Limit Exceeded");
			own_exit_code = EXIT_OLE;
			break;

			// FIXME: this is mostly MLE (but, probably, not always)
		/*case 6:
			asprintf(&result, "MLE - Memory Limit Exceeded");
			own_exit_code = EXIT_MLE;
			break;*/

		default:
			asprintf(&result, "RE - Runtime Error (Signal %d = %s)",
					signal_number, signal_name);
			own_exit_code = EXIT_RE;
		}
	}
	else if (0 != exit_code)
	{
		asprintf(&result, "RE - Runtime Error (Non-zero exit code - %d)",
				exit_code);
		own_exit_code = EXIT_OK;
	}
	else
	{
		//usleep(100);
		//sleep(1);
		asprintf(&result, "OK - OK");
		own_exit_code = EXIT_OK;
	}

	// Waiting for threads
	if (0 != thread_input_writer)
	{
		log("Waiting for thread_input_writer");
		if (0 != pthread_join(thread_input_writer, NULL))
			log("pthread_join(thread_input_writer, NULL) = %s", strerror(errno));
		thread_input_writer = 0;
	}
	if (0 != thread_output_reader)
	{
		log("Waiting for thread_output_reader");
		if (0 != pthread_join(thread_output_reader, NULL))
			log("pthread_join(thread_output_reader, NULL) = %s", strerror(errno));
		thread_output_reader = 0;
	}
	if (0 != thread_error_reader)
	{
		log("Waiting for thread_error_reader");
		if (0 != pthread_join(thread_error_reader, NULL))
			log("pthread_join(thread_error_reader, NULL) = %s", strerror(errno));
		thread_error_reader = 0;
	}
	// Killing threads
	if (0 != thread_input_writer)
	{
		log("Trying to kill thread_input_writer");
		if (0 != pthread_kill(thread_input_writer, SIGKILL))
			log("pthread_kill(thread_input_writer, NULL) = %s", strerror(errno));
	}
	if (0 != thread_output_reader)
	{
		log("Trying to kill thread_output_reader");
		if (0 != pthread_kill(thread_output_reader, SIGKILL))
			log("pthread_kill(thread_output_reader, NULL) = %s", strerror(errno));
	}
	if (0 != thread_error_reader)
	{
		log("Trying to kill thread_error_reader");
		if (0 != pthread_kill(thread_error_reader, SIGKILL))
			log("pthread_kill(thread_error_reader, NULL) = %s", strerror(errno));
	}

	// Summary information
	printf("<%s> execution result: %s\n", program_name, result);
	printf("CPU time used:\t%d\n", cpu_used);
	printf("Real time used:\t%d\n", real_used);
	printf("Memory used:\t%d\n", memory_used);
	printf("Output used:\t%d\n", output_used);
	printf("Exit code:\t%d\n", exit_code);
	printf("Reason:\t%s\n", term_reason);
	printf("Own exit code:\t%d\n", own_exit_code);

	log_finalize();
	return own_exit_code;
}


// issue #4 possible fix
int socket_OK = 1;

void sigpipe_handler(int a)
{
	if (socket_OK)
		log("SIGPIPE caught");
    socket_OK = 0;
}

int main(int argc, char *argv[])
{
	// issue #4 possible fix
    signal(SIGPIPE, &sigpipe_handler);

	/* Resolve application name */
	app_name = strdup(argv[0]);

	/* Parse command line */
	parse_command_line(argc, argv);

	/* Get program name, that should be runned */
	if (optind < argc)
	{
		program_name = argv[optind];
	}
	else
	{
		usage();
		exit(1);
	}

	// chroot
	if (NULL != root_directory)
	{
		//log("Setting root directory to %s", root_directory);
		if (chdir(root_directory) != 0)
			fatal_exit("chdir", strerror(errno), -1);
	}

	if (NULL != log_filename)
	{
		log_file = fopen(log_filename, "w+");
	}

	/*log("*** Runner params begin\n");
	for (int i = 0; i < argc; i++)
		log("= %s\n", argv[i]);
	log("*** Runner params end\n");*/

	// Logging options
	log("Runner options:");
	log("Command:\t\t%s", program_name);
	log("CPU time limit:\t\t%s ms", cpu_limit > 0 ? itoa(cpu_limit) : "(inf)");
	log("Real time limit:\t%s ms", real_limit > 0 ? itoa(real_limit) : "(inf)");
	log("Memory limit:\t\t%s bytes", memory_limit > 0 ? itoa(memory_limit) : "(inf)");
	log("Output limit:\t\t%s bytes", output_limit > 0 ? itoa(output_limit) : "(inf)");
	log("");
	log("Input file : \"%s\"", input_file);
	log("Output file: \"%s\"", output_file);
	log("Error file : \"%s\"", error_file);
	log("");

	//TODO: check for input file presence
	//TODO: 'pipe' return value
	if (NULL != input_file)
	{
		log("Creating pipe for input stream redirection to %s", input_file);
		if (pipe(pipein) != 0)
			fatal_exit("pipe(pipein)", strerror(errno), -1);
	}

	if (NULL != output_file)
	{
		log("Creating pipe for output stream redirection to %s", output_file);
		if (pipe(pipeout) != 0)
			fatal_exit("pipe(pipeout)", strerror(errno), -1);
		output_used = 0;
	}

	if (NULL != error_file)
	{
		log("Creating pipe for error stream redirection to %s", error_file);
		if (pipe(pipeerr) != 0)
			fatal_exit("pipe(pipeerr)", strerror(errno), -1);
	}

	child_pid = fork();
	if (child_pid < 0)
	{
		//// ERROR
		fatal_exit("fork", strerror(errno), -1);
	}
	else if (child_pid == 0)
	{
		//// CHILD PROCESS
		do_child(&argv[optind]);
	}
	else
	{
		//// PARENT PROCESS
		return do_parent();
	}
	return 1;
}
